package com.atguigu.gmall.index.controller;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.GmallIndexApplication;
import com.atguigu.gmall.index.feign.GmallPmsClient;
import com.atguigu.gmall.index.service.IndexService;
import com.atguigu.gmall.pms.entity.CategoryBrandEntity;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import org.apache.commons.lang.StringUtils;
import org.checkerframework.checker.units.qual.A;
import org.omg.CORBA.TIMEOUT;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.unbescape.css.CssIdentifierEscapeLevel;

import java.security.Key;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 跳转到首页 并加载三级分类
 *
 * @author Jia Fa
 * @create 2021-12-09 16:50
 */
@Controller
public class IndexController {
    @Autowired
    private IndexService indexService;

    @Autowired
    private StringRedisTemplate redisTemplate;  //使用StringRedisTemplate实现子类可以查看缓存的数据,对开发友好,默认是JDK存储二进制文件

    @Autowired
    GmallPmsClient pmsClient;
    @Autowired
    private RedissonClient redissonClient;

    private static final String KEY_PREFIX = "index:cates:";
    private static final String LOCK_PREFIX = "index:cate:lock:";

    @GetMapping
    public String toIndex(Model model) {
        // 查询一级分类到页面  给异步请求提供二级分类的pid
        // 查询Redis数据是否存在
        String jsonP1 = this.redisTemplate.opsForValue().get(KEY_PREFIX + 0L);
        if (StringUtils.isNotBlank(jsonP1)){
            List<CategoryEntity> categoryEntities = JSON.parseArray(jsonP1, CategoryEntity.class);
            model.addAttribute("categories", categoryEntities);
        }

        List<CategoryEntity> categoryEntities = this.indexService.queryCategoriesById();

        model.addAttribute("categories", categoryEntities);
        // 设置到Redis
        if (CollectionUtils.isEmpty(categoryEntities)){
            // DB查询为null  设置进Redis 防止缓存穿透
            this.redisTemplate.opsForValue().set(KEY_PREFIX+0L, null, 5, TimeUnit.MICROSECONDS);
        }else {
            // 设置到Redis  设置不同的key随机过期时间 防止缓存雪崩
            this.redisTemplate.opsForValue().set(KEY_PREFIX+0L, JSON.toJSONString(categoryEntities), 30+new Random().nextInt(10), TimeUnit.DAYS);
        }

        return "index";
    }

    /**
     * 页面异步加载获取二级分类及旗下子分类
     *
     * @param pid
     * @return
     */
    @GetMapping("index/cates/{pid}")
    @ResponseBody
    public ResponseVo<List<CategoryEntity>> queryCategoriesWithSubsByPid(@PathVariable("pid") Long pid) {

        List<CategoryEntity> categoryEntities = this.indexService.queryCategoriesWithSubsByPid(pid);
        return ResponseVo.ok(categoryEntities);

    }

    /**
     * 页面异步加载获取二级分类及旗下子分类
     *
     * @param pid
     * @return
     */
//    @GetMapping("index/cates/{pid}")
    @ResponseBody
    public ResponseVo<List<CategoryEntity>> queryCategoriesWithSubsByPid2(@PathVariable("pid") Long pid) {

        // 从Redis中查询是否存在缓存  存在缓冲穿透问题(大量请求访问不存在的数据,请求从Redis到mysql,导致宕机)
        // 解决方案 空结果也进行缓存 缓存为null值,存在时间很短  高级: 布隆过滤器
        String json = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
        //如果 String 不为空且不为 null 且不为空格，则为true
        if (StringUtils.isNotBlank(json)) {
            // 需要进行json转list
            List<CategoryEntity> categoryEntities = JSON.parseArray(json, CategoryEntity.class);
            return ResponseVo.ok(categoryEntities);
        }
        // 缓存击穿 热点Key过期
        // 在要去数据库查询的地方加上分布式锁 非公平锁 获取后再查询一次Redis中是否已经存在该数据了
        RLock lock = this.redissonClient.getLock(LOCK_PREFIX + pid);
        lock.lock();
        // 再查缓存 是否有其他线程存入了
        json = this.redisTemplate.opsForValue().get(KEY_PREFIX + pid);
        if (StringUtils.isNotBlank(json)) {
            // 需要进行json转list
            List<CategoryEntity> categoryEntities = JSON.parseArray(json, CategoryEntity.class);
            return ResponseVo.ok(categoryEntities);
        }
        //DB查询
        ResponseVo<List<CategoryEntity>> categories = this.pmsClient.categoriesWithSubsByPid(pid);
        List<CategoryEntity> categoryEntities = categories.getData();

        //查询结果是否为空
        if (CollectionUtils.isEmpty(categoryEntities)) {
            // 查询结果为null也进行保存,设置时间很短 改善缓存穿透问题 5min  使用布隆过滤器

            // 注意 此处的key值不能用上面的json 因为上面的可能为空
            this.redisTemplate.opsForValue().set(KEY_PREFIX + pid, null, 5, TimeUnit.MICROSECONDS);
        } else {
            // 将数据库查询结果存入Redis缓存中
            // 多个缓存同时过期,造成缓存雪崩问题 解决方案 为多个缓存结果设置随机的缓存有效时间
            // 单个热点Key过期时瞬时大量请求访问 造成缓存击穿  解决方案 设置分布式锁
            this.redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categoryEntities), 30 + new Random().nextInt(10), TimeUnit.DAYS);
        }

        return ResponseVo.ok(categoryEntities);

    }

    @GetMapping("index/testlock")
    @ResponseBody
    public ResponseVo<Object> testLock(){
       indexService.testLock();
       return ResponseVo.ok(null);
    }

    // 读写锁读锁
    @GetMapping("index/read/lock")
    @ResponseBody
    public ResponseVo<Object> readLock(){
        indexService.readLock();
        return ResponseVo.ok("读写锁读锁");
    }

    // 读写锁写锁
    @GetMapping("index/read/write")
    @ResponseBody
    public ResponseVo<Object> writeLock(){
        indexService.writeLock();
        return ResponseVo.ok("读写锁写锁");
    }

    //信号量（Semaphore） 抢车位
    @GetMapping("index/write/lock")
    @ResponseBody
    public ResponseVo<Object> semaphoreLock(){
        indexService.writeLock();
        return ResponseVo.ok(null);
    }

    //闭锁（CountDownLatch） 班长锁门了
    @GetMapping("index/latch/lock")
    @ResponseBody
    public ResponseVo<Object> latchLock(){
        indexService.latchLock();
        return ResponseVo.ok("班长锁门了...");
    }

    //闭锁（CountDownLatch） 出来1位同学
    @GetMapping("index/countDown/lock")
    @ResponseBody
    public ResponseVo<Object> countDown(){
        indexService.countDown();
        return ResponseVo.ok("出来1位同学...");
    }
}

